package ru.apertum.qsystem.server.controller;

import com.github.mustachejava.DefaultMustacheFactory;
import com.github.mustachejava.Mustache;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.extern.log4j.Log4j2;
import org.eclipse.jetty.server.Request;
import ru.apertum.qsystem.common.cmd.CmdParams;
import ru.apertum.qsystem.common.cmd.RpcStandInService;
import ru.apertum.qsystem.common.model.QCustomer;
import ru.apertum.qsystem.server.http.CommandHandler;
import ru.apertum.qsystem.server.model.QService;
import ru.apertum.qsystem.server.model.QServiceTree;
import ru.apertum.qsystem.server.model.QUser;
import ru.apertum.qsystem.server.model.QUserList;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

@Log4j2
public class QrExecutor {

    private static final String ERROR_HTML = "<html><body>ERROR!</body>";

    private static final LoadingCache<String, Mustache> FILE_CACHE = CacheBuilder.newBuilder().expireAfterWrite(20, TimeUnit.SECONDS).build(new CacheLoader<String, Mustache>() {
        @Override
        public Mustache load(String fileName) {
            try {
                final String pattern = new String(Files.readAllBytes(Paths.get(fileName)), StandardCharsets.UTF_8);
                return new DefaultMustacheFactory().compile(new StringReader(pattern), "screen");
            } catch (IOException e) {
                log.error("Error read file " + fileName, e);
                return new DefaultMustacheFactory().compile(new StringReader(ERROR_HTML), "screen_error");
            }
        }
    });

    private Mustache loadFile(String fileName) {
        try {
            return FILE_CACHE.get(fileName);
        } catch (ExecutionException e) {
            log.error("Error read from cache. File " + fileName, e);
            return new DefaultMustacheFactory().compile(new StringReader(ERROR_HTML), "screen_error");
        }
    }

    @Data
    @AllArgsConstructor
    public static class Response {
        private int status = 200;
        private String contentType = "";
        private String content = "";
    }


    /**
     * Обрабатываем запрос по запросу  в QR-code о отдаем ответ.
     *
     * @param target      uri.
     * @param baseRequest http запрос.
     * @param request     какойто запрос.
     * @param response    http ответ.
     * @return Ответ, включая статус и контент.
     */
    public Response handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) {
        log.trace("Run for {}", target);


        // Находим катомера в очередях или у операторов по ID пришедшем в куках.
        Found found = findCustomerByCookie(request.getCookies());
        if (found.needCleanCookieAndRedirect) {
            // поставим куки, чтоб не искать кастомера при повторном запросе.
            response.setHeader("Set-Cookie", "qsystem-customer-id=forget; max-age=-1; httpOnly;");
            response.setHeader("Location", CommandHandler.INFO_URL_QR);
            return new Response(HttpServletResponse.SC_TEMPORARY_REDIRECT, "", "");
        }
        if (found.customer == null) {
            if (baseRequest.getParameterMap().size() == 0) {
                //нет запроса никакого. Просто показываем заставочку.
                return new Response(HttpServletResponse.SC_OK, "text/html;charset=utf-8",
                        loadFile("config/qrcode_resources/screen_index.html").execute(new StringWriter(), new Object[0]).toString());
            }

            // тут можно потом показать на экране список услуг и продвигаться по дереву передавая rootId в параметре.
            final String rootId = baseRequest.getParameter("rootId");
            if (rootId != null && rootId.matches("\\d+|root")) {
                final long serviceId = "root".equals(rootId) ? QServiceTree.getInstance().getRoot().getId() : Long.parseLong(rootId);
                if (QServiceTree.getInstance().hasById(serviceId)) {
                    final QService folder = QServiceTree.getInstance().getById(serviceId);
                    final Map<String, Object> map = new HashMap<>();
                    map.put("services", folder.getChildren());
                    return new Response(HttpServletResponse.SC_OK, "text/html;charset=utf-8",
                            loadFile("config/qrcode_resources/screen_servicesList.html").execute(new StringWriter(), map).toString());
                }
            }

            // проверим наличие указанной услуги.
            final String serviceIdParam = baseRequest.getParameter("serviceId");
            if (serviceIdParam != null && serviceIdParam.matches("\\d+")) {
                final long serviceId = Long.parseLong(serviceIdParam);
                if (QServiceTree.getInstance().hasById(serviceId)) {
                    found.customer = standInService(serviceId, request.getServerName(), baseRequest.getLocalAddr().getBytes());
                    if (found.customer != null) {
                        // поставим куки, чтоб найти кастомера при повторном запросе.
                        response.setHeader("Set-Cookie", "qsystem-customer-id=" + found.customer.getId() + "; max-age=" + (2 * 60 * 60) + "; httpOnly;");
                    }
                }
            }
        } else {
            // нашелся в куках, а может быть хочет уйти.
            final String goodby = baseRequest.getParameter("goodby");
            if ("true".equalsIgnoreCase(goodby) && found.customer.getState().isWaiting()) {
                found.customer.getService().removeCustomer(found.customer);
                // поставим куки, чтоб не искать кастомера при повторном запросе.
                response.setHeader("Set-Cookie", "qsystem-customer-id=forget; max-age=-1; httpOnly;");
                response.setHeader("Location", CommandHandler.INFO_URL_QR);
                return new Response(HttpServletResponse.SC_TEMPORARY_REDIRECT, "", "");
            }
        }
        if (found.customer == null) {
            return new Response(HttpServletResponse.SC_OK, "text/html;charset=utf-8",
                    loadFile("config/qrcode_resources/screen_error.html").execute(new StringWriter(), new Object[0]).toString());
        } else {
            final Map<String, Object> map = new HashMap<>();
            map.put("service", found.customer.getService());
            map.put("customer", found.customer);
            map.put("user", found.customer.getUser());
            return new Response(HttpServletResponse.SC_OK, "text/html;charset=utf-8",
                    loadFile("config/qrcode_resources/screen_ticket.html").execute(new StringWriter(), map).toString());
        }
    }

    private QCustomer standInService(long serviceId, String ipAdress, byte[] ip) {
        QCustomer customer = null;
        // создаем кастомера вызвав задание по созданию кастомера.
        final CmdParams params = new CmdParams();
        params.serviceId = serviceId;
        params.priority = 1;
        try {
            // ставим в очередь нового кастомера по QR.
            final RpcStandInService rpcStandInService = Executer.getInstance().addCustomerTask.process(params, ipAdress, ip);
            if (rpcStandInService.getResult().getError() == null) {
                customer = rpcStandInService.getResult().getCustomer();
            } else {
                log.error("Customer does not sent to queue. Reason: {}", rpcStandInService.getResult().getError());
            }
        } catch (Exception ex) {
            log.error("Customer does not sent to queue.", ex);
        }
        return customer;
    }

    @Data
    private static class Found {
        boolean needCleanCookieAndRedirect = false;
        QCustomer customer = null;
    }

    private Found findCustomerByCookie(Cookie[] cookies) {
        final Found found = new Found();
        if (cookies != null) {
            for (Cookie cookie : cookies) {
                if ("qsystem-customer-id".equals(cookie.getName())) {
                    String custId = cookie.getValue();
                    if (custId.matches("\\d+")) {
                        QCustomer customer = findCustomerInServer(Long.parseLong(custId));
                        if (customer != null) {
                            found.customer = customer;
                        } else {
                            found.needCleanCookieAndRedirect = true;
                        }
                    }
                }
            }
        }
        return found;
    }

    private QCustomer findCustomerInServer(Long id) {
        for (QService service : QServiceTree.getInstance().getNodes()) {
            final QCustomer customer;
            if ((customer = service.findOutCustomerById(id)) != null) {
                return customer;
            }
        }
        for (QUser user : QUserList.getInstance().getItems()) {
            if ((user.getCustomer() != null && user.getCustomer().getId().equals(id))) {
                return user.getCustomer();
            }
        }
        return null;
    }
}
